using System;
using System.Net;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Xml;
using System.Threading;
using System.Security.Cryptography;

using EEVENTMANAGERMGRLib;

using FreeFlow;
using System.Runtime.InteropServices;

namespace FreeFlow.Client
{
  /// <summary>The type of connection to the engine.</summary>
  public enum ConnectionType
  {
    /// <summary>DCOM will be used (only recommended for internal network use).</summary>
    DCOM,
    /// <summary>HTTP will be used.</summary>
    HTTP
  }

  /// <summary>Defines how flags will be raised against the engine</summary>
  public enum RaiseFlagBy
  {
    /// <summary>Flags will be raised via the engine's XML interface</summary>
    TransactionProtocol,
    /// <summary>
    /// Flags will be raised via the engine's DCOM interface. 
    /// Raising flags through the engine's DCOM interface does not support passing a username and password.
    /// </summary>
    DCOM
  }

  /// <summary>
  /// Class used to connect to the engine. All communication with the engine is through this class. 
  /// Configuration is via the <see cref="ConnectionType"/>, <see cref="Engine"/> and <see cref="HttpServer"/> properties. 
  /// If HTTP is used, a HTTP connection is used to connect to the web server and then DCOM/COM is used to connect to the engine.
  /// If a more configurable setup is required then contact the author.
  /// </summary>
  [Serializable]
  public class Connection : IGetAttachment
  {
    /// <summary>
    /// Creates a new <see cref="Connection"/> instance where an existing session ID is available.
    /// </summary>
    /// <param name="sessionId">Existing session ID.</param>
    public Connection(string sessionId) : this()
    {
      this.sessionId = sessionId;
    }

    /// <summary>
    /// Creates a new <see cref="Connection"/> instance.
    /// </summary>
    public Connection()
    {
    }

    #region properties
    private string sessionId;
    /// <summary>
    /// Gets the user's session ID.
    /// </summary>
    public string SessionId
    {
      get { return sessionId; }
    }

    private string userName;
    /// <summary>
    /// Gets the user's login name, if the user has logged-in via the login methods.
    /// </summary>
    public string UserName
    {
      get
      {
        return userName;
      }
    }

    private string httpServer = "localhost";
    /// <summary>
    /// Gets or sets the HTTP server to connect to. Use the EScriptUrl property if more control
    /// over the URL is required.
    /// </summary>
    public string HttpServer 
    {
      get { return httpServer; }
      set { httpServer = value; }
    }

    private string escriptsUrl;
    /// <summary>
    /// Gets or sets the URL used to access the escript virtual directory. 
    /// e.g. http://webserver/escripts 
    /// </summary>
    public string EscriptsUrl
    {
      get { return escriptsUrl; }
      set { escriptsUrl = value; }
    } 

    private string engine = "localhost";
    /// <summary>
    /// Gets or sets the name of the machine where the engine is running.
    /// </summary>
    public string Engine 
    {
      get { return engine; }
      set { engine = value; }
    }

    private int sap;
    /// <summary>
    /// Gets or sets the SAP that will be used when logging in.
    /// </summary>
    public int SAP
    {
      get { return sap; }
      set { sap = value; }
    }

     private string clientType = "Win32";
    /// <summary>
    /// Gets or sets the ClientType string that is passed to the engine with a login request.
    /// </summary>
    public string ClientType
    {
      get { return clientType; }
      set { clientType = value; }
    }

    private ConnectionType connectionType = ConnectionType.HTTP;
    /// <summary>
    /// Gets or sets the type of connection that will be used to connect to the engine.
    /// </summary>
    public ConnectionType ConnectionType
    {
      get { return connectionType; }
      set { connectionType = value; }
    }

    #endregion
    #region login
    /// <summary>
    /// Encodes a string using the MD5 algorithm.
    /// </summary>
    /// <param name="password">Password.</param>
    /// <returns>The encoded password</returns>
    public static string MD5Encode(string password)
    {
      // MD5 encode the password
      using (MemoryStream ms = new MemoryStream())
      using (StreamWriter sw = new StreamWriter(ms))
      {
        sw.Write(password);
        sw.Flush();
        ms.Position = 0;
  			
        MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
        byte[] Hash = MD5.ComputeHash(ms);
  			
        // convert bytes to hex digits
        StringBuilder HashString = new StringBuilder();
        foreach (byte item in Hash)
        {
          HashString.Append(item.ToString("X2"));
        }
        return HashString.ToString();
      }
    }

    private XmlDocument InternalLoginForm()
    {
      XmlDocument doc = new XmlDocument();
      XmlElement docElem = doc.CreateElement("eLoginRequest");
      docElem.SetAttribute("ClientType", clientType);
      docElem.SetAttribute("CurrentSAP", sap.ToString());
      doc.AppendChild(docElem);
      XmlDocument response = SendDecoratedMsg(doc);
      GetUserDetails(response);
      return response;
    }

    /// <summary>
    /// Sends an initial login request to the engine. If the engine automatically logs the user in (due to SSO for instance) 
    /// then this method will log the user in and return null. If eUser authentication is being used, 
    /// Connection.LogOn can be used instead.
    /// <br/><br/>
    /// When SSO is to be used from an ASP.NET application, ensure Windows Authentication is the only authentication mechanism enabled for the application 
    /// and ensure impersonation is enabled, either for the whole application using &lt;identity impersonate= "true"/&gt; in web.config or via code in the application.  
    /// </summary>
    /// <returns>The form to be rendered, or null if the user has been automatically logged in.</returns>
    /// <example>
    /// This example illustrates logging into the engine.
    /// <code language="c#">
    /// Connection conn = new FreeFlow.Client.Connection();
    /// conn.HttpServer = "my server";
    /// conn.Engine = "my server";
    /// Form form = conn.LogOnForm();
    /// 
    /// // fields to be set depends on the SAP being used.
    /// form.Field("username").Value = "user name";
    /// form.Field("password").Value = Connection.MD5Encode("password");
    /// 
    /// conn.LogOn(form); 
    /// </code>
    /// </example>
    public Form LogOnForm()
    {
      Form form = null;
      XmlDocument response = InternalLoginForm();
      XmlElement docElem = response.DocumentElement;
      if (docElem.Name == "eLoginFormResponse")
      {
        form = new FreeFlow.Client.Form(docElem[FreeFlow.Client.Form.FormLayoutElement], this);
        form.FillUsage(docElem["FieldUsageList"]);
        form.FillValues(docElem["FieldOutputList"]);
      }
      return form;
    }

    /// <summary>
    /// Logs in the specified user to the engine. This attempts to use eUser authentication.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <param name="password">Password.</param>
    /// <example>
    /// This example illustrates logging into the engine.
    /// <code language="c#">
    /// Connection conn = new FreeFlow.Client.Connection();
    /// conn.HttpServer = "my server";
    /// conn.Engine = "my server";
    /// conn.LogOn("user name", "password");
    /// </code>
    /// <code language="vb">
    /// Dim conn As New FreeFlow.Client.Connection
    /// conn.HttpServer = "my server"
    /// conn.Engine = "my server"
    /// conn.LogOn("user name", "password")
    /// </code>
    /// </example>
    public void LogOn(string userName, string password)
    {
      XmlDocument doc = new XmlDocument();
      XmlElement docElem = doc.CreateElement("eLoginRequest");
      doc.AppendChild(docElem);
      docElem.SetAttribute("ClientType", clientType);
      docElem.SetAttribute("CurrentSAP", sap.ToString());

      // field input list
      XmlElement fieldInputList = doc.CreateElement("FieldInputList");
      docElem.AppendChild(fieldInputList);
        
      // user name
      XmlElement userNameInput = doc.CreateElement("FieldInput");
      userNameInput.SetAttribute("Field", "username");
      userNameInput.InnerText = userName;
      fieldInputList.AppendChild(userNameInput);

      // user name
      XmlElement passwordInput = doc.CreateElement("FieldInput");
      passwordInput.SetAttribute("Field", "password");
      passwordInput.InnerText = MD5Encode(password);
      fieldInputList.AppendChild(passwordInput);

      InternalLogin2(doc);
    }

    /// <summary>
    /// Sends a login request to the engine for an associated login form.
    /// </summary>
    /// <param name="form">The filled form used to attempt the login</param>
    public void LogOn(Form form)
    {
      XmlDocument doc = new XmlDocument();
      XmlElement docElem = doc.CreateElement("eLoginRequest");
      docElem.SetAttribute("ClientType", clientType);
      docElem.SetAttribute("CurrentSAP", sap.ToString());
      if (form != null)
      {
        form.Validate();
        form.AddFieldInputList(docElem);
      }
      doc.AppendChild(docElem);
      InternalLogin2(doc);
    }

    private void InternalLogin2(XmlDocument doc)
    {
      XmlDocument response = SendDecoratedMsg(doc);
      GetUserDetails(response);
    }

    private void GetUserDetails(XmlDocument response)
    {
      if (response.DocumentElement.Attributes["SessionID"] != null)
      {
        sessionId = response.DocumentElement.Attributes["SessionID"].Value;
      }
      if (response.DocumentElement.Attributes["UserID"] != null)
      {
        userName = response.DocumentElement.Attributes["UserID"].Value;
      }
    }
	
    /// <summary>
    /// Logs out the user from the engine.
    /// </summary>
    public void Logout()
    {
      try
      {
        SendDecoratedMsg("<eLogoutRequest />");
      }
      catch (FreeFlowException)
      {
        // ignore failed logouts - only likely to occur if we are already logged out
      }
      sessionId = string.Empty;
      userName = string.Empty;
    }

    /// <summary>
    /// Gets a value indicating whether the connection is logged in.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if logged in; otherwise, <c>false</c>.
    /// </value>
    public bool LoggedIn
    {
      get
      {
        return !string.IsNullOrEmpty(sessionId);
      }
    }

    #endregion
    #region sending messages
    /// <summary>
    /// Adds session ID and locale info to a request before sending it to the engine.
    /// </summary>
    /// <param name="msg">The engine request.</param>
    /// <returns>The engine response.</returns>
    public string SendDecoratedMsg(string msg)
    {
      XmlDocument XMLDoc = new XmlDocument();
      XMLDoc.LoadXml(msg);
      return SendDecoratedMsg(XMLDoc).OuterXml;
    }

    /// <summary>
    /// Adds session ID and locale info to a request before sending it to the engine.
    /// </summary>
    /// <param name="msg">The engine request in an XML document.</param>
    /// <returns>The engine response.</returns>
    public XmlDocument SendDecoratedMsg(XmlDocument msg)
    {
      if (msg != null)
      {
        // add the session ID
        if (SessionId != null)
        {
          XmlAttribute SessionAttr = msg.CreateAttribute("SessionID");
          SessionAttr.Value = SessionId;
          msg.DocumentElement.SetAttributeNode(SessionAttr);
        }

        // add the locale
        XmlAttribute LocaleAttr = msg.CreateAttribute("Locale");
        LocaleAttr.Value = Thread.CurrentThread.CurrentCulture.ThreeLetterISOLanguageName;
        msg.DocumentElement.SetAttributeNode(LocaleAttr);
  			
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(SendMsg(msg.OuterXml));
        return doc;
      }
      return null;
    }
	
    /// <summary>
    /// Sends a request to the engine.
    /// </summary>
    /// <param name="msg">The engine request.</param>
    /// <returns>The engine response.</returns>
    public string SendMsg(string msg)
    {	
      if (Engine == null)
        throw new FreeFlowException("Engine has not been specified.");

      UdpXmlDebugger.SendMessage(msg);

      string response;
      switch (connectionType)
      {
        case ConnectionType.DCOM :
          response = SendDcomMsg(msg);
          break;
        case ConnectionType.HTTP :
          response = SendHttpMsg(msg);
          break;
        default :
          throw new FreeFlowException("Unrecognised connection type - " + connectionType.ToString());
      }

      UdpXmlDebugger.SendMessage(response);

      XmlDocument doc = new XmlDocument();
      doc.LoadXml(response);
      if (doc.DocumentElement.LocalName == "eErrorResponse")
      {
        StringBuilder builder = new StringBuilder();
        XmlElement exceptionNode = doc.DocumentElement["Exception"];
        while (exceptionNode != null)
        {
          builder.Append(exceptionNode["Value"].ChildNodes[0].Value);
          exceptionNode = exceptionNode["Exception"];
          if (exceptionNode != null)
            builder.Append(Environment.NewLine);
        }
        
        throw new FreeFlowException(builder.ToString());
      }

      return response;
    }

    private string SendDcomMsg(string msg)
    {
      // try creating from Prog ID
      Type type = Type.GetTypeFromProgID("e-work.EworkTransaction", Engine);
      // if that doesn't work try using the GUID
      if (type == null)
        type = Type.GetTypeFromCLSID(new Guid("{EBAC9532-3094-11D4-9B1F-00C04F2A1F69}"), Engine, true);
      if (type == null)
        throw new FreeFlowException("Unable to find the e-work.EworkTransaction object, ensure eworkTransaction.dll is registered");
      
      Object objTest = Activator.CreateInstance(type);
      
      // call EworkTransaction via reflection (since the returned object may not be of the right type)
      string response = (string)objTest.GetType().InvokeMember("EworkTransaction", 
        BindingFlags.InvokeMethod, null, objTest, new object[1]{msg});

      Marshal.ReleaseComObject(objTest);

      return response;
    }

    private string SendHttpMsg(string msg)
    {
      if (httpServer == null)
        throw new FreeFlowException("HttpServer has not been specified.");

      // construct URL
      string URL = "http://" + httpServer + "/escripts/";
      if (!string.IsNullOrEmpty(escriptsUrl))
        URL = escriptsUrl;
      if (!URL.EndsWith("/"))
        URL += "/";
      URL += "eMessageHandler.dll";

      // construct message
      string ServerNode	= "<Engine Name=\"Engine1\"><Transport Type=\"DCOM\"><Server>" + 
        engine +  "</Server></Transport></Engine>";
      string parameters	= "ServerNode=" + HttpUtility.UrlEncode(ServerNode) + 
        "&Request=" + HttpUtility.UrlEncode(msg);
		
      Uri uri = new Uri(URL);
      HttpWebRequest httpWebRequest	= (HttpWebRequest) WebRequest.Create(uri);
      httpWebRequest.Credentials = CredentialCache.DefaultCredentials;
      httpWebRequest.PreAuthenticate = true;
      httpWebRequest.Method	= "POST";
      httpWebRequest.ContentType = "application/x-www-form-urlencoded";
		
      // TODO - handle other encoding types
      Encoding encoding = Encoding.Default;

      // get length of request (may well be a better way to do this)
      MemoryStream memStream = new MemoryStream();
      StreamWriter streamWriter = new StreamWriter(memStream, encoding);
      streamWriter.Write(parameters);
      streamWriter.Flush();
      httpWebRequest.ContentLength = memStream.Length;
      streamWriter.Close();

      Stream stream = httpWebRequest.GetRequestStream();
      streamWriter = new StreamWriter(stream, encoding);
      streamWriter.Write(parameters);
      streamWriter.Close();

      using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
      using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
      {
        return streamReader.ReadToEnd();
      }
    }
    #endregion
    #region flag raising
    
    private RaiseFlagBy raiseFlagBy = RaiseFlagBy.TransactionProtocol;
    /// <summary>Defines how flags will be raised against the engine. 
    /// By default, flags will be raised via the engine's XML interface. 
    /// If you want to raise flags from an ASP.NET page using the ActiveX control, 
    /// the account under which ASP.NET is running must hold the 'Flag Raiser' COM+ role.</summary>
    public RaiseFlagBy RaiseFlagBy
    {
      get
      {
        return raiseFlagBy;
      }
      set
      {
        raiseFlagBy = value;
      }
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <remarks>When no user name is specified, public access must be enabled.</remarks>
    /// <param name="flag">Flag name.</param>
    /// <param name="flagData">Flag data.</param>
    public void RaiseFlag(string flag, object[] flagData)
    {
      RaiseFlag(null, null, flag, null, flagData);
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <param name="password">Password.</param>
    /// <param name="flag">Flag name.</param>
    /// <param name="folderId">The ID of the folder on whose behalf the flag will be raised.</param>
    /// <param name="flagData">Flag data.</param>
    /// <example>
    /// This example illustrates raising a flag against the engine.
    /// <code>
    /// [C#]
    /// Connection conn = new Connection();
    /// conn.HttpServer = "my server";
    /// conn.Engine = "my server";
    /// conn.RaiseFlag("user name", "password", "my flag", "folder id", new object[] {1, "flag data 2"});
    /// </code>
    /// </example>
    public void RaiseFlag(string userName, string password, string flag, string folderId, object[] flagData)
    {
      string[] flagDataStrings = new string[flagData.Length];
      for (int i = 0; i < flagData.Length; i++)
      {
        if (flagData[i] != null)
          flagDataStrings[i] = flagData[i].ToString();
      }
      RaiseFlag(userName, password, flag, folderId, flagDataStrings);
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <param name="password">Password.</param>
    /// <param name="flag">Flag name.</param>
    /// <param name="folderId">The ID of the folder on whose behalf the flag will be raised.</param>
    /// <param name="flagData">Flag data.</param>
    /// <example>
    /// This example illustrates raising a flag against the engine.
    /// <code>
    /// [C#]
    /// Connection conn = new Connection();
    /// conn.HttpServer = "my server";
    /// conn.Engine = "my server";
    /// conn.RaiseFlag("user name", "password", "my flag", "folder id", new string[] {"flag data 1", "flag data 2"});
    /// </code>
    /// </example>
    public void RaiseFlag(string userName, string password, string flag, string folderId, string[] flagData)
    {
      switch (raiseFlagBy)
      {
        case RaiseFlagBy.TransactionProtocol:
          RaiseFlagByTP(userName, password, flag, folderId, flagData);
          break;
        case RaiseFlagBy.DCOM:
          RaiseFlagByDCOM(userName, password, flag, folderId, flagData);
          break;
        default:
          throw new FreeFlowException("Unrecognized RaiseFlagBy type - " + raiseFlagBy.ToString());
      }
    }

    private void RaiseFlagByDCOM(string userName, string password, string flag, string folderId, string[] flagData)
    {
      if ((userName != null) || (password != null))
        throw new FreeFlowException("Raising flags through the engine's DCOM interface does not support passing a username and password");

      Type type = Type.GetTypeFromProgID("e-work.EventManager", Engine);
      if (type == null)
        throw new FreeFlowException("Unable to find the e-work.EventManager object, ensure eEventMgr.dll is registered");
      Object objTest = Activator.CreateInstance(type);
      EventManager manager = (EventManager)objTest;

      string flagDataString = string.Empty;
      if (flagData != null)
      {
        for (int i = 0; i < flagData.Length; i++)
        {
          if (i > 0)
            flagDataString = flagDataString + '\t';
          flagDataString = flagDataString + flagData[i];
        }
      }
      manager.RaiseFlag(flag, folderId, flagDataString); 
    }

    private void RaiseFlagByTP(string userName, string password, string flag, string folderId, string[] flagData)
    {
      XmlDocument doc = new XmlDocument();
      XmlElement docElem = doc.CreateElement("eRaiseFlagRequest");
      if (userName != null)
        docElem.SetAttribute("UserID", userName);
      if (password != null)
        docElem.SetAttribute("Password", MD5Encode(password));
      docElem.SetAttribute("Flag", flag);
      if (folderId != null)
        docElem.SetAttribute("FolderID", folderId);

      // flag data
      if (flagData != null)
      {
        XmlElement flagDataListElement = doc.CreateElement("FlagDataList");
        docElem.AppendChild(flagDataListElement);
        foreach (string flagDataItem in flagData)
        {
          XmlElement flagDataElement = doc.CreateElement("FlagData");
          flagDataElement.InnerText = flagDataItem;
          flagDataListElement.AppendChild(flagDataElement);
        }
      }

      doc.AppendChild(docElem);
      SendDecoratedMsg(doc);
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <remarks>When no user name is specified, public access must be enabled.</remarks>
    /// <param name="flag">Flag name.</param>
    /// <param name="flagData">Flag data.</param>
    public void RaiseFlag(string flag, string[] flagData)
    {
      RaiseFlag(null, null, flag, null, flagData);
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <remarks>When no user name is specified, public access must be enabled.</remarks>
    /// <param name="flag">Flag name.</param>
    public void RaiseFlag(string flag)
    {
      RaiseFlag(null, null, flag, null, null);
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <param name="password">Password.</param>
    /// <param name="flag">Flag.</param>
    public void RaiseFlag(string userName, string password, string flag)
    {
      RaiseFlag(userName, password, flag, null, null);
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <param name="userName">Name of the user.</param>
    /// <param name="password">Password.</param>
    /// <param name="flag">Flag name.</param>
    /// <param name="flagData">Flag data.</param>
    public void RaiseFlag(string userName, string password, string flag, string[] flagData)
    {
      RaiseFlag(userName, password, flag, null, flagData);
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <param name="folderId">The ID of the folder on whose behalf the flag will be raised.</param>
    /// <param name="flag">Flag name.</param>
    public void RaiseFlag(string folderId, string flag)
    {
      RaiseFlag(null, null, flag, folderId, null);
    }

    /// <summary>
    /// Raises a flag against the engine.
    /// </summary>
    /// <param name="folderId">The ID of the folder on whose behalf the flag will be raised.</param>
    /// <param name="flag">The flag name.</param>
    /// <param name="flagData">The flag data.</param>
    public void RaiseFlag(string folderId, string flag, string[] flagData)
    {
      RaiseFlag(null, null, flag, folderId, flagData);
    }

    #endregion
    #region attachments
    /// <summary>
    /// Gets an attachment from the engine. Also see <see cref="AttachmentField"/>
    /// </summary>
    /// <param name="type">The type of the attachment.</param>
    /// <param name="file">The name of the file.</param>
    /// <param name="owner">The owner of the attachment (folder ID for folder attachments, map name for map attachments, procedure name for procedure attachments).</param>
    /// <returns>A byte array containing the attachment data.</returns>
    public byte[] GetAttachment(AttachmentType type, string file, string owner)
    {
      // get the attachment
      XmlDocument doc = new XmlDocument();
      XmlElement docElem = doc.CreateElement("eAttachmentRequest");
      docElem.SetAttribute("File", file);
      docElem.SetAttribute("Owner", owner);

      // attachment type
      string typeString = null;
      if (type == AttachmentType.Procedure)
        typeString = "Procedure";
      else if (type == AttachmentType.Folder)
        typeString = "Folder";
      else
        throw new FreeFlowException("Unrecognized attachment type - " + type.ToString());
      docElem.SetAttribute("Type", typeString);

      doc.AppendChild(docElem);
      XmlDocument resp = SendDecoratedMsg(doc);

      string data = resp.DocumentElement["Value"].InnerText;

      // decode and return
      return Convert.FromBase64String(data);
    }

    /// <summary>
    /// Sends an attachment to the engine and stores it in the eAttachment table for the specified folder.
    /// Also see <see cref="AttachmentField"/>
    /// </summary>
    /// <param name="file">File name to be used for this attachment.</param>
    /// <param name="folderId">Folder ID.</param>
    /// <param name="data">Attachment data.</param>
    /// <returns>The name of the attached file (may not be the same as the passed in filename)</returns>
    public string SetAttachment(string file, string folderId, byte[] data)
    {
      XmlDocument doc = new XmlDocument();
      XmlElement docElem = doc.CreateElement("ePostAttachmentRequest");
      docElem.SetAttribute("File", file);
      docElem.SetAttribute("FolderID", folderId);

      doc.AppendChild(docElem);

      XmlElement valueNode = doc.CreateElement("Value");
      XmlCDataSection dataNode = doc.CreateCDataSection(Convert.ToBase64String(data));
      valueNode.AppendChild(dataNode);
      docElem.AppendChild(valueNode);
      
      XmlDocument response = SendDecoratedMsg(doc);
      return response.DocumentElement.Attributes["File"].Value;
    }

    /// <summary>
    /// Saves the specified attachment to a file on disk.
    /// </summary>
    /// <param name="type">The type of attachment.</param>
    /// <param name="file">The attachment filename.</param>
    /// <param name="owner">The owner of the attachment.</param>
    /// <param name="fileName">Name of the file to save to.</param>
    public void SaveAttachmentToFile(AttachmentType type, string file, string owner, string fileName)
    {
      byte[] data = GetAttachment(type, file, owner);
      using (FileStream writer = new FileStream(fileName, FileMode.Create))
      {
        writer.Write(data, 0, data.Length);
      }
    }

    #endregion
  }
}
